#pragma once
#include <osg/Array>
#include <osg/Matrix>
#include <osg/DisplaySettings>
#include <glm/glm.hpp>
#include <iomanip>
#include <sstream>
#include "core/CPointxz.h"
#include "core/CVector.h"
#include "core/CLog.h"

/**
mat[16] = {
	'a', 'b', 'c', 'd',
	'e', 'f', 'g', 'h',
	'i', 'j', 'k', 'l',
	'm', 'n', 'o', 'p'
}
@note GLM: Memory Layout:Column-Major(Column-first) => {'a', 'b', 'c', 'd'} is the first Column(0) => Column 2 && row 1 == 'g' = mat[col][row] = mat[2][1]
@note OSG: Memory Layout:      Row-Major(Row-first) => {'a', 'b', 'c', 'd'} is the first Row(0)    => column 2 && Row 1 == 'g' = mat(row, col) = mat(1, 2)
@note
	glm::translate(MAT, translation) == MAT * glm::translate(glm::mat4(1), translation)
	glm::rotate(MAT, angle, axis) == MAT * glm::rotate(glm::mat4(1), angle, axis)
	glm::scale(MAT, scale) == MAT * glm::scale(glm::mat4(1), scale)
*/

class OUtil {
public:
	static int refCount(osg::Referenced* refObject) { return refObject->referenceCount(); }// for debug object's memory some

	static osg::Vec3 toVec3(const P3D& vertex) { return osg::Vec3(vertex.x, vertex.y, vertex.z); }
	static P3D fromVec3(const osg::Vec3& vertex) { return P3D(vertex.x(), vertex.y(), vertex.z()); }

	static osg::Matrixf toMatrix(const glm::mat4& mat) {
		return osg::Matrixf(// Column-Major -> Row-Major
			mat[0][0], mat[0][1], mat[0][2], mat[0][3],
			mat[1][0], mat[1][1], mat[1][2], mat[1][3],
			mat[2][0], mat[2][1], mat[2][2], mat[2][3],
			mat[3][0], mat[3][1], mat[3][2], mat[3][3]);
	}

	static osg::Vec2Array* toVec2Array(const CVector<P>& vertices) {
		auto osgVertices = new osg::Vec2Array;
		osgVertices->assign(
			reinterpret_cast<const osg::Vec2*>(vertices.data()),
			reinterpret_cast<const osg::Vec2*>(vertices.data() + vertices.size())
		);
		return osgVertices;
	}

	static osg::Vec2Array* toVec2Array(const CVector<P2D>& coords) {
		auto osgCoords = new osg::Vec2Array;
		osgCoords->assign(
			reinterpret_cast<const osg::Vec2*>(coords.data()),
			reinterpret_cast<const osg::Vec2*>(coords.data() + coords.size())
		);
		return osgCoords;
	}

	static osg::Vec3Array* toVec3Array(const CVector<P3D>& vertices) {
		auto osgVertices = new osg::Vec3Array;
		osgVertices->assign(
			reinterpret_cast<const osg::Vec3*>(vertices.data()),
			reinterpret_cast<const osg::Vec3*>(vertices.data() + vertices.size())
		);
		return osgVertices;
	}

	static osg::Vec3Array* toVec3Array(const CVector<P>& vertices, float y) {
		auto osgVertices = new osg::Vec3Array;
		for (const auto& p : vertices)
			osgVertices->push_back(osg::Vec3(p.x, y, p.z));
		return osgVertices;
	}

	static osg::IntArray* toIntArray(const CVector<int>& values) {
		auto osgInts = new osg::IntArray;
		osgInts->assign(
			reinterpret_cast<const int*>(values.data()),
			reinterpret_cast<const int*>(values.data() + values.size())
		);
		return osgInts;
	}

	static osg::FloatArray* toFloatArray(const CVector<float>& values) {
		auto osgFloats = new osg::FloatArray;
		osgFloats->assign(
			reinterpret_cast<const float*>(values.data()),
			reinterpret_cast<const float*>(values.data() + values.size())
		);
		return osgFloats;
	}

	static void checkMultiSamplesStatus() {
		bool isMultiSamplesEnabled = osg::DisplaySettings::instance()->getMultiSamples();
		if (isMultiSamplesEnabled) {
			int numSamples = osg::DisplaySettings::instance()->getNumMultiSamples();
			LogI << "MultiSamples is enabled, NumMultiSamples = " << numSamples;
		} else
			LogW << "MultiSamples is not enabled!";
	}

	/**
	* @param corner bottom-left vertex
	* @example corner=(-1,-1,0), widthVec=(2,0,0), heightVec=(0,2,0)
	*/
	static osg::Geometry* createTexturedQuadGeometry(const P3D& corner, const P3D& widthVec, const P3D& heightVec, osg::Texture2D* texture, int textureUnit = 0) {
		auto quadGeometry = osg::createTexturedQuadGeometry(OUtil::toVec3(corner), OUtil::toVec3(widthVec), OUtil::toVec3(heightVec));
		quadGeometry->getOrCreateStateSet()->setTextureAttributeAndModes(textureUnit, texture);
		return quadGeometry;
	}

	static void printMatrix(const std::string& name, const osg::Matrixf& matrix) {
		std::cout << name << ":\n";
		for (int col = 0; col < 4; ++col) {
			std::cout << "  [ ";
			for (int row = 0; row < 4; ++row) {
				std::cout << std::setw(8) << std::fixed << std::setprecision(3) << matrix(row, col);
				if (row < 3) std::cout << ", ";
			}
			std::cout << " ]\n";
		}
		std::cout << std::endl;
	}

	static void printMatrix(const std::string& name, const glm::mat4& matrix) {
		std::cout << name << ":\n";
		for (int col = 0; col < 4; ++col) {
			std::cout << "  [ ";
			for (int row = 0; row < 4; ++row) {// column major: matrix[col][row]
				std::cout << std::setw(8) << std::fixed << std::setprecision(3) << matrix[col][row];
				if (row < 3) std::cout << ", ";
			}
			std::cout << " ]\n";
		}
		std::cout << std::endl;
	}

private:
	OUtil() = default;
};